<?php 
defined('MALL_VERSION') or die('No direct script access.');
/*
121.199.53.221 coin.mall.ffrj.net
121.199.53.221 manager.mall.ffrj.net
121.199.53.221 shop.mall.ffrj.net
121.199.53.221 center.mall.ffrj.net
121.199.53.221 shop-web.mall.ffrj.net
 */

define('CONDITION_DOMAIN', 'manage.ffrj.net');
define('COIN_DOMAIN', 'coin.mall.fenfenriji.com');
define('SHOP_DOMAIN', 'shop.mall.fenfenriji.com');
define('CENTER_DOMAIN', 'center.mall.fenfenriji.com');
define('REDIS_HOST', '10.161.163.63');
define('REDIS_PORT', 6379);
define('REDIS_AUTH', '');

/**
 * Set the default time zone.
 *
 * @link http://kohanaframework.org/guide/using.configuration
 * @link http://www.php.net/manual/timezones
 */
date_default_timezone_set('Asia/Shanghai');

/**
 * Set the default locale.
 *
 * @link http://kohanaframework.org/guide/using.configuration
 * @link http://www.php.net/manual/function.setlocale
 */
setlocale(LC_ALL, 'en_US.utf-8');

/**
 * 商城类api
 * 
 * Usage
 * $api = MallApi::instance();
 * // 附上uid, token
 * $api->attachUser('12344555', '8e16992c2323');
 * 
 * // 调用签到
 * $coin = $api->getUserCoin();
 * if (!$coin) {
 * 		echo $api->last_error->message;
 * 		return;
 * }
 * 
 * // 正常操作
 * ...
 * 
 * @author Administrator
 *
 */
class MallApi {
	
	public static $task_share = 'user:share';
	public static $task_bmitesting = 'bmi:testing';
	public static $shop_exchange = 'shop:exchange';
	public static $task_topic_elite = 'user:topic:elite';
	public static $task_system_award = 'system:award';
	public static $task_system_biz_youmi_video = 'biz:youmi:video';
	public static $task_system_ad = 'system:ad';
	private static $_instance;

	private static $_logDir = 'logs';
	
	public static function instance($userAuth) {
		if (!MallApi::$_instance) {
			MallApi::$_instance = new self($userAuth);
		}
		
		return MallApi::$_instance;
	}
	
	/**
	 * @var APIError
	 */
	public $last_error;
	
	protected $userAuth;
	
	private function __construct($userAuth) {
		$this->userAuth = $userAuth;
	}
	
	/**
	 * @return array 正常
	 * @return false 错误，请使用last_error获得详细错误
	 */
	public function getCoinRuleList()
	{
		return $this->reqeust(COIN_DOMAIN, 'rule/list');
	}
	
	public function getCoinTaskList()
	{
		return $this->reqeust(COIN_DOMAIN, 'task/list');
	}

	public function getLogList($page = 1, $pagesize = 20, $order_by = 'id', $sort = 'DESC')
	{
		$params = array('start' => ($page -1) * $pagesize, 'limit' => $pagesize);
		if ($order_by) {
			$params = array_merge($params, array('order_by' => $order_by, 'sort' => $sort));
		}

		return $this->reqeust(COIN_DOMAIN, 'log/list', $params);
	}

	public function getTaskTopLog($trigger, $limit = 20)
	{
		return $this->reqeust(COIN_DOMAIN, 'log/list', array('trigger' => $trigger, 'limit' => $limit, 'order_by' => 'id', 'sort' => 'DESC'));
	}
	
	public function executeCoinTask($trigger, $amount = 0, $remark = '')
	{
		return $this->reqeust(COIN_DOMAIN, 'task/execute', array('trigger'=>$trigger, 'amount' => $amount, 'remark' => $remark));
	}
	
	public function getCoinExchangeLog($remark)
	{
	    return $this->reqeust(COIN_DOMAIN, 'log/query', array('trigger' => MallApi::$shop_exchange, 'remark' => $remark));
	}

	public function getTaskInfo($trigger)
	{
		return $this->reqeust(COIN_DOMAIN, 'task/info', array('trigger'=>$trigger));
	}
	
	public function getUserCoin()
	{
		return $this->reqeust(COIN_DOMAIN, 'user/coin');
	}
	
	public function openEnvelope($key)
	{
		return $this->reqeust(COIN_DOMAIN, 'envelope/open', array('key'=>$key));
	}

	public function openTurtable($is_free)
	{
		return $this->reqeust(COIN_DOMAIN, 'turntable/open', array('is_free'=>$is_free));
	}

	// 获得任务条件
	public function getTaskCondition($trigger)
	{
		return $this->reqeust(CONDITION_DOMAIN, 'admin_task.php', array('action'=>'doTask','task'=>$trigger));
	}
	
	private function reqeust($domain, $uri, $params = array()) {
		if (!$this->userAuth) {
			$this->last_error = new APIError(10000, 'Need attachUser first');
			return false;
		}
		
		$url = "http://{$domain}/{$uri}";
		$params = $this->userAuth->params() + $params;
		$body = MallApi::getHttpResponse($url, $params);

		$result = json_decode($body, true);
		if ($result) {
			if ($result['returnData']) {
				return $result['returnData'];
			} else if ($result['resultData']) {
				return $result['resultData'];
			} else if (isset($result['errorNo']) and $result['errorMsg']) {
				$this->last_error = new APIError($result['errorNo'], $result['errorMsg']);
				return false;
			}
		}

		$this->last_error = new APIError(10000, '服务器内部错误');
		return false;
	}
	
	public static function getHttpResponse($url, $params) {
		// 拼装get参数
		$url .= '?'.http_build_query($params);

		$curl = curl_init($url);
		curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
		curl_setopt($curl, CURLOPT_USERAGENT, "FFWEB_1.0-API");
		curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);
		curl_setopt($curl, CURLOPT_TIMEOUT, 30);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);// 显示输出结果
		curl_setopt($curl, CURLOPT_ENCODING, "");
		curl_setopt($curl, CURLOPT_HEADER, FALSE);
		curl_setopt($curl, CURLOPT_USERPWD, 'xxt:xxt123');
		$responseText = curl_exec($curl);
		$info = curl_getinfo($curl);

		// file_put_contents(time().'.log', "[{$url}]\n$responseText");
		// var_dump( curl_error($curl) );exit;//如果执行curl过程中出现异常，可打开此开关，以便查看异常内容
		curl_close($curl);

		if (empty($info['http_code']) or $info['http_code'] != 200) {
			//MallApi::addLog('ERROR', ':url[:code]::responseText', array(':url' => $url,':code' => $info['http_code'], ':responseText' => $responseText));
			return FALSE;
		}

		// MallApi::addLog('DEBUG', ':url[:code]::responseText', array(':url' => $url,':code' => $info['http_code'], ':responseText' => $responseText));

		return $responseText;
	}
	
	public static function getHttpResponsePOST($url, $para, $input_charset = '') {
		$curl = curl_init($url);
		curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头
		curl_setopt($curl,CURLOPT_POST,true); // post传输数据
		curl_setopt($curl,CURLOPT_POSTFIELDS,$para);// post传输数据
		curl_setopt($curl, CURLOPT_USERAGENT, "FFWEB_1.0-API");
		curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 30);
		curl_setopt($curl, CURLOPT_TIMEOUT, 30);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);// 显示输出结果
		curl_setopt($curl, CURLOPT_ENCODING, "");
		curl_setopt($curl, CURLOPT_HEADER, FALSE);
		$responseText = curl_exec($curl);
		$info = curl_getinfo($curl);
	
		// var_dump( curl_error($curl) );exit;//如果执行curl过程中出现异常，可打开此开关，以便查看异常内容
		curl_close($curl);

		if (empty($info['http_code']) or $info['http_code'] != 200) {
			return FALSE;
		}

		return $responseText;
	}

	/*
	 *     MallApi::addLog('DEBUG', 'Could not locate user: :user', array(
	 *         ':user' => $username,
	 *     ));
	 */
	public static function addLog($level, $message, array $values = NULL, array $additional = NULL)
	{
		if ($values)
		{
			// Insert the values into the message
			$message = strtr($message, $values);
		}

		// Grab a copy of the trace
		if (isset($additional['exception']))
		{
			$trace = $additional['exception']->getTrace();
		}
		else
		{
			// Older php version don't have 'DEBUG_BACKTRACE_IGNORE_ARGS', so manually remove the args from the backtrace
			if ( ! defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$trace = array_map(function ($item) {
					unset($item['args']);
					return $item;
				}, array_slice(debug_backtrace(FALSE), 1));
			}
			else
			{
				$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 1);
			}
		}

		if ($additional == NULL)
		{
			$additional = array();
		}

		// Create a new message
		$messages[] = array
		(
			'time'       => time(),
			'level'      => strtoupper($level),
			'body'       => $message,
			'trace'      => $trace,
			'file'       => isset($trace[0]['file']) ? $trace[0]['file'] : NULL,
			'line'       => isset($trace[0]['line']) ? $trace[0]['line'] : NULL,
			'class'      => isset($trace[0]['class']) ? $trace[0]['class'] : NULL,
			'function'   => isset($trace[0]['function']) ? $trace[0]['function'] : NULL,
			'additional' => $additional,
		);

		MallApi::writeLog($messages);
	}

	public static function writeLog(array $messages)
	{
		// Set the yearly directory name
		$directory = MallApi::$_logDir.DIRECTORY_SEPARATOR.date('Y');

		if ( ! is_dir($directory))
		{
			// Create the yearly directory
			mkdir($directory, 02777);

			// Set permissions (must be manually set to fix umask issues)
			chmod($directory, 02777);
		}

		// Add the month to the directory
		$directory .= DIRECTORY_SEPARATOR.date('m');

		if ( ! is_dir($directory))
		{
			// Create the monthly directory
			mkdir($directory, 02777);

			// Set permissions (must be manually set to fix umask issues)
			chmod($directory, 02777);
		}

		// Set the name of the log file
		$filename = $directory.DIRECTORY_SEPARATOR.date('d').'.log';

		if ( ! file_exists($filename))
		{
			// Create the log file
			file_put_contents($filename, "<?php defined('VERSION') or die('No direct script access.'); ?>".PHP_EOL);

			// Allow anyone to write to log files
			chmod($filename, 0666);
		}

		foreach ($messages as $message)
		{
			// Write each message into the log file
			file_put_contents($filename, PHP_EOL.MallApi::format_message($message), FILE_APPEND);
		}
	}

	/**
	 * Formats a log entry.
	 *
	 * @param   array   $message
	 * @param   string  $format
	 * @return  string
	 */
	public static function format_message(array $message, $format = "time --- level: body in file:line")
	{
		$message['time'] = date('Y-m-d H:i:s');

		$string = strtr($format, array_filter($message, 'is_scalar'));

		if (isset($message['additional']['exception']))
		{
			// Re-use as much as possible, just resetting the body to the trace
			$message['body'] = $message['additional']['exception']->getTraceAsString();

			$string .= PHP_EOL.strtr($format, array_filter($message, 'is_scalar'));
		}

		return $string;
	}
}

class UserAuth {
	protected $key = 'UserAuth';
	public $uid;
	public $token;
	public $time;
	
	public static function factory($uid, $token, $key = 'hash') {
		if ($key === 'hash') {
			return new InternalUserAuth($uid, $token);
		} else {
			return new ExternalUserAuth($uid, $token);
		}
	}

	public function __construct($uid, $token) {
		$this->uid = $uid;
		$this->token = $token;
		$this->time = time();
	}

	public function params() {
		return array();
	}
}

// 内部使用hash
class InternalUserAuth extends UserAuth  {
	public $hash;
	
	public function __construct($uid, $token) {
		parent::__construct($uid, $token);
		$this->hash = $this->generateHash();
	}
	
	public function generateHash()
	{
		return sha1($this->key.$this->uid.$this->token.$this->time);
	}

	public function params()
	{
		return array('uid'=>$this->uid, 'time'=>$this->time, 'token'=>$this->token, 'hash'=>$this->hash);
	}
}

// 外部使用identify
class ExternalUserAuth extends UserAuth  {
	public $identify;
	
	public function __construct($uid, $token) {
		parent::__construct($uid, $token);
		$this->identify = $this->request();
	}
	
	public function request()
	{
		$domain = COIN_DOMAIN;
		$uri = 'user/coin';
		$params = array('renew'=>'renew');
		
		$url = "http://{$domain}/{$uri}";
		$body = MallApi::getHttpResponse($url, $params);
		$result = json_decode($body, true);
		if ($result) {
			if ($result['errorNo'] && $result['errorNo'] == 10031) {
				$ts = explode(' ', $result['errorMsg']);
				$newIdentify = $ts[1];
				return $newIdentify;
			}
		}

		die('Server Auth Error');
	}

	public function params()
	{
		return array('uid'=>$this->uid, 'time'=>$this->time, 'token'=>$this->token, 'identify'=>$this->identify);
	}
}

class RedisClient {

	static $_instance;

	public static function factory() {
		if(!isset(RedisClient::$_instance)) {
			$c = __CLASS__;
			RedisClient::$_instance = new $c(REDIS_HOST, REDIS_PORT, REDIS_AUTH);
		}

		return RedisClient::$_instance;
	}

	private $hashTokenCache = 'Hash:token:';

	private $redis;
	private function __construct($host, $port, $auth) {
		$this->redis = new Redis();
		$this->redis->connect($host, $port);

		if (!empty($auth)) {
			$this->redis->auth($auth);
		}
	}

	public function getToken($uid) {
        $result = $this->redis->hGetAll($this->hashTokenCache.$uid);
        if (!empty($result)) {
            return $result;
        }

        return false;
    }
}

class APIError extends Exception {
	
	public $code;
	public $message;
	
	public function __construct($code, $message) {

		// Pass the message and integer code to the parent
		parent::__construct($message, (int) $code, NULL);

		// Save the unmodified code
		// @link http://bugs.php.net/39615
		$this->code = $code;
		$this->message = $message;
	}
}
